package org.wattdepot.simulation.oahugrid.main.batch;

import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import org.wattdepot.simulation.oahugrid.demandcurve.DailyDemandCurve;
import org.wattdepot.simulation.oahugrid.demandcurve.StandardDemandCurve;
import org.wattdepot.simulation.oahugrid.demandcurve.WeekendDemandCurve;
import org.wattdepot.simulation.oahugrid.powerplant.PowerPlantSet;
import org.wattdepot.simulation.oahugrid.strategy.ByPeakTypeStrategy;
import org.wattdepot.simulation.oahugrid.strategy.SimulationEntry;
import org.wattdepot.simulation.oahugrid.strategy.SimulationStrategy;
import static org.wattdepot.simulation.oahugrid.powerplant.PowerPlantSet.*;

/**
 * Top-level class for generating a simulated power grid data set.
 * 
 * @author Philip Johnson
 */
public class BatchSimulation {

  /** How dates will be formatted in simulation. */
  public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
  /** The columns of data produced by the simulation. */
  private String header = "Time, Plant, GridMW, OverheadMW, Type";
  /** The entries in the simulation dataset. */
  private List<SimulationEntry> entries = new ArrayList<SimulationEntry>();
  /** Random number seed. */
  private int seed = 0;
  /** A random generator for deciding whether to use low or high carbon plant sets. */
  private Random random = new Random(seed);
  /** Number of days to generate data for. */
  private int numDays;
  /** Default number of days to generate data for. */
  private static final int DEFAULT_NUM_DAYS = 30;
  /** Default start day for the simulated data. */
  private static final String DEFAULT_START_DAY = "2009-10-25";

  /**
   * Creates a new instance of a simulated data set.
   * 
   * @param startDay The day to start the simulation on.
   * @param numDays The number of days to run the simulation for.
   * @throws Exception If problems occur.
   */
  public BatchSimulation(String startDay, int numDays) throws Exception {
    this.numDays = numDays;
    GregorianCalendar day = Tstamp.makeTimestamp(startDay);
    // Create the data for 30 days starting today.
    for (int i = 0; i < numDays; i++) {
      // Randomly decide whether we are using low or high carbon plantsets.
      PowerPlantSet plantSet = (random.nextBoolean() ? LOW_CARBON_PLANTS : HIGH_CARBON_PLANTS);
      // Get the demand curve, either standard or weekend, depending on day, with some jiggle.
      DailyDemandCurve curve = getDemandCurve(day);
      // For now, always use ByPeakTypeStrategy.
      SimulationStrategy strategy = new ByPeakTypeStrategy();
      // Add the simulation entries.
      entries.addAll(strategy.generateDay(curve, plantSet));
      // Update the day to the next day.
      day.add(Calendar.DAY_OF_YEAR, 1);
    }
  }

  /**
   * Creates a new instance of a simulated data set.
   * 
   * @throws Exception If problems occur.
   */
  public BatchSimulation() throws Exception {
    this(DEFAULT_START_DAY, DEFAULT_NUM_DAYS);
  }

  /**
   * Returns a DailyDemandCurve given a day. Currently, the demand curve is based just on the day of
   * the week; we choose either the StandardDemandCurve or the WeekendDemandCurve. The weekend has a
   * 5 MW jiggle, the weekday has a 10 MW jiggle.
   * 
   * @param day The day for which we want a demand curve.
   * @return The DailyDemandCurve to be used for this day.
   */
  private DailyDemandCurve getDemandCurve(GregorianCalendar day) {
    // If it's a weekend, generate data using the weekend demand curve.
    if ((day.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY)
        || (day.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)) {
      return (new WeekendDemandCurve(day)).addRandomVariation(5);
    }
    else {
      return (new StandardDemandCurve(day)).addRandomVariation(10);
    }
  }

  /**
   * Writes out the simulation data.
   * 
   * @param path The path to write it out to.
   * @throws IOException If problems occur.
   */
  public void writeData(String path) throws IOException {
    String cr = System.getProperty("line.separator");
    FileWriter output = new FileWriter(path);
    output.append(header).append(cr);
    for (SimulationEntry entry : entries) {
      output.append(entry.getCsv()).append(cr);
    }
    output.close();
    System.out.println("Wrote " + path);
  }

  /**
   * Returns a String with the simulation data.
   * 
   * @return The simulation data.
   */
  public String getData() {
    String cr = System.getProperty("line.separator");
    StringBuffer buff = new StringBuffer(70 * 48961);
    buff.append(header).append(cr);
    for (SimulationEntry entry : entries) {
      buff.append(entry.getCsv()).append(cr);
    }
    return buff.toString();
  }

  /**
   * Returns the list of SimulationEntries for this simulation.
   * 
   * @return The SimulationEntry instances.
   */
  public List<SimulationEntry> getSimulationData() {
    return this.entries;
  }

  /**
   * @return the numDays
   */
  public int getNumDays() {
    return numDays;
  }

  /**
   * The top-level main method.
   * 
   * @param args First optional arg is a Tstamp-formatted date string that is used as the start
   * date, while the second optional arg is the number of days to emit data for.
   * @throws Exception if problems occur writing the file.
   */
  public static void main(String[] args) throws Exception {
    String startDay;
    int numDays;
    BatchSimulation simulation;
    if (args.length == 0) {
      simulation = new BatchSimulation();
    }
    else if (args.length == 1) {
      startDay = args[0];
      simulation = new BatchSimulation(startDay, DEFAULT_NUM_DAYS);
    }
    else if (args.length == 2) {
      startDay = args[0];
      numDays = Integer.valueOf(args[1]);
      simulation = new BatchSimulation(startDay, numDays);
    }
    else {
      System.err.println("Too many command line args provided.");
      return;
    }
    SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd.HH.mm", Locale.US);
    String fileName = "simulation-" + format.format(new Date()) + ".csv";
    simulation.writeData(fileName);
  }

}
